package com.ztesoft.book.core.wx.pay.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.ztesoft.book.common.constant.Constant;
import com.ztesoft.book.common.utils.exception.ExUtil;
import com.ztesoft.book.core.wx.pay.service.WechatPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * 微信支付
 *
 * @author 杨帆
 * 创建时间：2021-07-20
 */
@Slf4j
@Service("WechatPayService")
public class WechatPayServiceImpl implements WechatPayService {


    @Value("${book.pay.mchid}")
    private String merchantId;
    @Value("${book.pay.serialnum}")
    private String merchantSerialNumber;
    @Value("${book.pay.apiv3key}")
    private String apiV3Key;
    @Value("${book.pay.keypem}")
    private String merchantPrivateKeyPath;


    /**
     * 方法功能描述:
     * < 预支付订单统一生成方法 >
     *
     * @param goodsNode 参数说明
     * @return JSONObject
     * @author YangFan
     * @date 2021-07-28 16:30
     */
    @Override
    public String createPrepay(ObjectNode goodsNode) {
        HttpEntity entity = null;
        CloseableHttpResponse response = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(merchantPrivateKeyPath));

            //不需要传入微信支付证书了
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(merchantId,
                            new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                    apiV3Key.getBytes("utf-8"));

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));

            // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
            CloseableHttpClient httpClient = builder.build();

            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(bos, goodsNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            response = httpClient.execute(httpPost);

            //获取结果实体
            entity = response.getEntity();
            String bodyAsString = EntityUtils.toString(entity);
            JSONObject bodyObj = JSON.parseObject(bodyAsString);
            log.info("【预支付订单生成】返回参数：" + bodyObj);

            if (bodyObj.containsKey(Constant.CODE)) {
                ExUtil.throwEx(bodyObj.getString("code") + ":" + bodyObj.getString("message"));
            }

            return bodyObj.getString("prepay_id");
        }
        catch (IOException e) {
            ExUtil.throwEx("预支付IO异常：" + e.getMessage());
        }
        catch (Exception e) {
            ExUtil.throwEx("预支付异常：" + e.getMessage());
        }
        finally {
            try {
                EntityUtils.consume(entity);
                if (Objects.nonNull(response)) {
                    response.close();
                }
            }
            catch (IOException e) {
                ExUtil.throwEx("预支付流关闭异常：" + e.getMessage());
            }
        }
        return "";
    }


    /**
     * 方法功能描述:
     * < 预支付ID签名 >
     *
     * @param prepayId 参数说明
     * @param appId    参数说明
     * @return JSONObject
     * @author YangFan
     * @date 2021-07-29 08:45
     */
    @Override
    public JSONObject wxTuneUp(String prepayId, String appId) {
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID()
                .toString()
                .replace("-", "");
        String packageStr = "prepay_id=" + prepayId;
        ArrayList<String> list = new ArrayList<>();
        list.add(appId);
        list.add(time);
        list.add(nonceStr);
        list.add(packageStr);

        JSONObject jsonObject = new JSONObject();
        try {
            //加载签名
            String packageSign = sign(StrUtil.bytes(buildSignMessage(list)));
            jsonObject.put("appid", appId);
            jsonObject.put("timeStamp", time);
            jsonObject.put("nonceStr", nonceStr);
            jsonObject.put("packages", packageStr);
            jsonObject.put("signType", "RSA");
            jsonObject.put("paySign", packageSign);
        }
        catch (Exception e) {
            ExUtil.throwEx("签名异常：" + e.getMessage());
        }
        return jsonObject;
    }

    /**
     * 方法功能描述:
     * < 构造签名串 >
     *
     * @param signMessage 参数说明
     * @return String
     * @author YangFan
     * @date 2021-07-29 08:39
     */
    private String buildSignMessage(List<String> signMessage) {
        if (CollUtil.isEmpty(signMessage)) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str)
                    .append("\n");
        }
        return sbf.toString();
    }


    /**
     * 方法功能描述:
     * < 生成签名 >
     *
     * @param message 参数说明
     * @return String
     * @author YangFan
     * @date 2021-07-29 08:41
     */
    private String sign(byte[] message) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(merchantPrivateKeyPath));
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder()
                    .encodeToString(sign.sign());
        }
        catch (NoSuchAlgorithmException | FileNotFoundException | InvalidKeyException | SignatureException ex) {
            ExUtil.throwEx("生成签名异常");
            return null;
        }
    }


    @Override
    public JSONObject notify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>(12);
        try {
            String result = readData(request);
            // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
            String plainText = verifyNotify(result, apiV3Key);
            if (StrUtil.isNotEmpty(plainText)) {
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "成功");
            }
            else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
            return JSONObject.parseObject(plainText);
        }
        catch (Exception e) {
            ExUtil.throwEx("notify：微信通知回调异常" + e.getMessage());
        }
        return null;
    }


    private String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();

            br.lines().forEach(item -> {
                result.append(item);
            });

            return result.toString();
        }
        catch (IOException e) {
            ExUtil.throwEx("readData：服务器异常");
        }
        finally {
            if (br != null) {
                try {
                    br.close();
                }
                catch (IOException e) {
                    ExUtil.throwEx("readData：IO流异常");
                }
            }
        }
        return "";
    }


    private String verifyNotify(String body, String key) {
        // 获取平台证书序列号
        cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);
        String event_type = resultObject.getStr("event_type");
        if (event_type.indexOf("SUCCESS") > 0) {
            cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");
            String cipherText = resource.getStr("ciphertext");
            String nonceStr = resource.getStr("nonce");
            String associatedData = resource.getStr("associated_data");
            AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
            // 密文解密
            try {
                return aesUtil.decryptToString(
                        associatedData.getBytes(StandardCharsets.UTF_8),
                        nonceStr.getBytes(StandardCharsets.UTF_8),
                        cipherText
                );
            }
            catch (Exception e) {
                ExUtil.throwEx("verifyNotify异常");
                return null;
            }
        }
        else {
            ExUtil.throwEx("【event_type】返回非成功：" + event_type);
        }
        return "";
    }


    @Override
    public JSONObject createRefund(ObjectNode goodsNode) {
        HttpEntity entity = null;
        CloseableHttpResponse response = null;
        JSONObject bodyObj = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(merchantPrivateKeyPath));

            //不需要传入微信支付证书了
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(merchantId,
                            new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                    apiV3Key.getBytes("utf-8"));

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));

            // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
            CloseableHttpClient httpClient = builder.build();

            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(bos, goodsNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            response = httpClient.execute(httpPost);

            //获取结果实体
            entity = response.getEntity();
            String bodyAsString = EntityUtils.toString(entity);
            bodyObj = JSON.parseObject(bodyAsString);
            log.info("【退款申请】返回参数：" + bodyObj);

            if (bodyObj.containsKey(Constant.CODE)) {
                ExUtil.throwEx(bodyObj.getString("code") + ":" + bodyObj.getString("message"));
            }

            return bodyObj;
        }
        catch (IOException e) {
            ExUtil.throwEx("退款申请IO异常：" + e.getMessage());
        }
        catch (Exception e) {
            ExUtil.throwEx("退款异常：" + e.getMessage());
        }
        finally {
            try {
                EntityUtils.consume(entity);
                if (Objects.nonNull(response)) {
                    response.close();
                }
            }
            catch (IOException e) {
                ExUtil.throwEx("退款流关闭异常：" + e.getMessage());
            }
        }
        return bodyObj;
    }


    @Override
    public JSONObject getOrder(String orderId) {
        HttpEntity entity = null;
        CloseableHttpResponse response = null;
        JSONObject bodyObj = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(merchantPrivateKeyPath));

            //不需要传入微信支付证书了
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(merchantId,
                            new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                    apiV3Key.getBytes("utf-8"));

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));

            // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
            CloseableHttpClient httpClient = builder.build();

            HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + orderId + "?mchid=" + merchantId);
            httpGet.addHeader("Accept", "application/json");
            response = httpClient.execute(httpGet);

            //获取结果实体
            entity = response.getEntity();
            String bodyAsString = EntityUtils.toString(entity);
            bodyObj = JSON.parseObject(bodyAsString);

            return bodyObj;
        }
        catch (IOException e) {
            ExUtil.throwEx("查单申请IO异常：" + e.getMessage());
        }
        catch (Exception e) {
            ExUtil.throwEx("查单异常：" + e.getMessage());
        }
        finally {
            try {
                EntityUtils.consume(entity);
                if (Objects.nonNull(response)) {
                    response.close();
                }
            }
            catch (IOException e) {
                ExUtil.throwEx("查单流关闭异常：" + e.getMessage());
            }
        }
        return null;
    }


    @Override
    public void closeOrder(String orderId) {
        CloseableHttpResponse response = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(merchantPrivateKeyPath));

            //不需要传入微信支付证书了
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(merchantId,
                            new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                    apiV3Key.getBytes("utf-8"));

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));

            // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
            CloseableHttpClient httpClient = builder.build();

            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + orderId + "/close");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid", merchantId);
            objectMapper.writeValue(bos, rootNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            response = httpClient.execute(httpPost);

        }
        catch (IOException e) {
            ExUtil.throwEx("关单申请IO异常：" + e.getMessage());
        }
        catch (Exception e) {
            ExUtil.throwEx("关单异常：" + e.getMessage());
        }
        finally {
            try {
                if (response != null) {
                    response.close();
                }
            }
            catch (IOException e) {
                ExUtil.throwEx("关单流关闭异常：" + e.getMessage());
            }
        }
    }


}
